#include <std.h>
#include <error.h>
#include <type.h>
#include <dint.h>
#include <biosdisk.h>
#include <memory.h>
#include <asm.h>

/*(Table 00234)
Values for disk operation status:
 00h	successful completion
 01h	invalid function in AH or invalid parameter
 02h	address mark not found
 03h	disk write-protected
 04h	sector not found/read error
 05h	reset failed (hard disk)
 05h	data did not verify correctly (TI Professional PC)
 06h	disk changed (floppy)
 07h	drive parameter activity failed (hard disk)
 08h	DMA overrun
 09h	data boundary error (attempted DMA across 64K boundary or >80h sectors)
 0Ah	bad sector detected (hard disk)
 0Bh	bad track detected (hard disk)
 0Ch	unsupported track or invalid media
 0Dh	invalid number of sectors on format (PS/2 hard disk)
 0Eh	control data address mark detected (hard disk)
 0Fh	DMA arbitration level out of range (hard disk)
 10h	uncorrectable CRC or ECC error on read
 11h	data ECC corrected (hard disk)
 20h	controller failure
 31h	no media in drive (IBM/MS INT 13 extensions)
 32h	incorrect drive type stored in CMOS (Compaq)
 40h	seek failed
 80h	timeout (not ready)
 AAh	drive not ready (hard disk)
 B0h	volume not locked in drive (INT 13 extensions)
 B1h	volume locked in drive (INT 13 extensions)
 B2h	volume not removable (INT 13 extensions)
 B3h	volume in use (INT 13 extensions)
 B4h	lock count exceeded (INT 13 extensions)
 B5h	valid eject request failed (INT 13 extensions)
 B6h	volume present but read protected (INT 13 extensions)
 BBh	undefined error (hard disk)
 CCh	write fault (hard disk)
 E0h	status register error (hard disk)
 FFh	sense operation failed (hard disk)
*/

#define BD 0x13

static unsigned block_write=0;

int cflag()
{
	int c;
	c=(_f&1);
	return c;
}

#define high(register) ((u8)(register>>8))

int reset_disk(u8 drive)
{
	_bioscall(0,0,0,(u16)drive,0,0,BD);
	if(cflag())	{
		return high(((u16)_eax));
	}
	return SUCCESS;
}

int reset_disk_retry(u8 drive,int retries)
{
	while(--retries>0)	{
		if((reset_disk(drive))==SUCCESS)
			break;
	}
	return NULL;
}

#define DRIVE 0x80

struct drive_struct	{
	int cylinders;
	int sectors;
	int heads;
}drive,myparameters;

int show_parameters(struct drive_struct *p)
{
	printk("[Cylinders:%d,sectors:%d,heads:%d]",p->cylinders,p->sectors,p->heads);
	return NULL;
}

int get_drive_parameters()
{
	int retries=3,cl;
	do	{
		_bioscall(0x0800,0,0,(u16)DRIVE,0,0,BD);
	}while((cflag()) && (--retries));
	if(cflag())	{
		error("Failed to get drive parameters.");
		return NULL;
	}
	cl=(u8)_ecx;
	drive.cylinders=high(((u16)_ecx));
	drive.cylinders|=((cl & 0xc0)<<2);
	drive.sectors=cl & 0x3f;
	drive.heads=high(((u16)_edx))+1;
	return SUCCESS;
}

int get_chs(u32 LBA)
{
	if(drive.sectors==0)	{
		stop("No drive parameters");
	}
	myparameters.sectors=(LBA%(drive.sectors))+1;
	myparameters.cylinders=(LBA/(drive.sectors))/(drive.heads);
	myparameters.heads=(LBA/(drive.sectors))%(drive.heads);
	return NULL;
}

int read_prim(u8 *buf,u32 LBA,u8 n)
{
	u8 ch,cl;
	u16 c;
	get_chs(LBA);
	ch=(u8)(myparameters.cylinders);
	cl=myparameters.sectors;
	cl|=(((myparameters.cylinders)&768)>>2);
	c=ch*0x100+cl;
	_bioscall(0x0200+n,0x1000,c,(myparameters.heads)*0x100+0x80,0,0,BD);
	if(cflag())	{
		return high(((u16)_eax));
	}
	memcpy(buf,(void *)0x1000,n*0x200);
	return NULL;
}

struct dap_struct	{
	u8 size;
	u8 rsvd;
	u16 n;
	u32 buf;
	u64 start;
}*dap=(struct dap_struct *)0x9000;

int check_ext()
{
	_bioscall(0x4100,0x55aa,0,0x0080,0,0,0x13);
	if((u16)_ebx==0xaa55)	{
	} else	{
	}
	return NULL;
}

int read_drive(void *buf,u32 LBA,u16 n)
{
	if(!buf)
		return EPARMNULL;
	dap->n=n;
	dap->buf=0x1000;
	dap->start=LBA;
	_bioscall(0x4200,0,0,0x80,(u32)dap,0,BD);
	memcpy(buf,(u8 *)0x1000,n*512);
	if((_f&1))
		return (u16)_eax;
	else
		return NULL;
}

#define SECT_SZ 512
#define MAX_READ 0x60

int read_drive_buf(u32 lba,u8 *start,u32 offset,u32 size)
{
	int err;
	u32 start_sector,nsect;
	start_sector=lba+offset/SECT_SZ;
	nsect=size/SECT_SZ+1;
	u8 *buf,*bbuf;
	buf=bbuf=(u8 *)get_mem(size+SECT_SZ);	/*Guard band to avoid a buffer overflow when doing integral sector reads*/
	while(nsect)	{
		if(nsect>MAX_READ)	{
			if(err=read_drive((void *)buf,start_sector,MAX_READ))
				return err;
			buf+=MAX_READ*SECT_SZ;
			start_sector+=MAX_READ;
			nsect-=MAX_READ;
		} else	{
			if(err=read_drive((void *)buf,start_sector,nsect))
				return err;
			nsect=NULL;
		}
	}
	memcpy(start,bbuf+offset,size);
	free(bbuf);
	return NULL;
}

int write_drive(void *buf,u32 LBA,u16 n)
{
	stop("\nwrite_drive:not allowed!");
	if(!buf)
		return EPARMNULL;
	dap->n=n;
	dap->buf=0x1000;
	dap->start=LBA;
	_bioscall(0x4300,0,0,0x80,(u32)dap,0,BD);
	memcpy(buf,(u8 *)0x1000,n*512);
	if((_f&1))
		return (u16)_eax;
	else
		return NULL;
}

int init_disk()
{
	drive.sectors=0;
	reset_disk_retry(0x80,3);
	get_drive_parameters();
	check_ext();
	create_setting_val("block_disk_write",&block_write);
	return SUCCESS;
} 

int reset_disk_drv()
{
	reset_disk(0x80);
	return NULL;
}

static unsigned write_device(unsigned p0,unsigned p1,unsigned p2,unsigned p3)
{
	if(block_write)
		return 0;
	switch(p0)	{
		case WRITE_DISK: return write_drive((void *)p1,p2,p3);
	}
	return 0;
}

static unsigned read_device(unsigned p0,unsigned p1,unsigned p2,unsigned p3)
{
	switch(p0)	{
		case READ_DISK: return read_drive((void *)p1,p2,p3);
	}
	return 0;
}

kdrv hddbios={
	.name="hddbios",
	.init=init_disk,
	.reset=reset_disk_drv,
	.deinit=NULL, 
	.read_device=read_device,
	.write_device=write_device,
	.device_irq=DEVICE_IRQ_UNUSED,
};

__init(hddbios_init,hddbios);
